package me.hekr.iotos.api;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import lombok.Data;
import lombok.SneakyThrows;
import me.hekr.iotos.api.dto.BatchAddDeviceResp;
import me.hekr.iotos.api.dto.CloudSendMsgDTO;
import me.hekr.iotos.api.dto.DelTopoResp;
import me.hekr.iotos.api.dto.DevIdListReq;
import me.hekr.iotos.api.dto.Device;
import me.hekr.iotos.api.dto.DeviceAddReq;
import me.hekr.iotos.api.dto.DeviceApiDTO;
import me.hekr.iotos.api.dto.DeviceDTO;
import me.hekr.iotos.api.dto.DevicePacketResp;
import me.hekr.iotos.api.dto.DeviceUpdateNameReq;
import me.hekr.iotos.api.dto.LoginRsp;
import me.hekr.iotos.api.dto.ModelProtocolDTO;
import me.hekr.iotos.api.dto.Product;
import me.hekr.iotos.api.dto.Snapshot;
import me.hekr.iotos.api.dto.klink.AddTopoResp;
import me.hekr.iotos.api.dto.klink.KlinkResp;
import me.hekr.iotos.api.dto.klink.ModelData;
import me.hekr.iotos.api.dto.klink.TopoSub;
import me.hekr.iotos.api.enums.DeviceType;
import me.hekr.iotos.api.enums.ErrorCode;
import me.hekr.iotos.api.exception.IotException;
import me.hekr.iotos.api.service.RetrofitIotService;
import me.hekr.iotos.api.util.JsonUtil;
import me.hekr.iotos.api.util.Pagec;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.logging.HttpLoggingInterceptor;
import okhttp3.logging.HttpLoggingInterceptor.Level;
import org.apache.commons.lang3.StringUtils;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;



public class IotClient {
  private static final String MAC_NAME = "HmacSHA1";
  private static final ObjectMapper objectMapper = new ObjectMapper();
  private static final Level logLevel = Level.BODY;

  static {
    objectMapper
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .configure(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, true);
  }

  private Retrofit retrofit;
  private Builder builder;
  private RetrofitIotService retrofitIotService;

  private IotClient(Builder builder) {
    this.builder = builder;
    if (StringUtils.isAnyBlank(builder.host, builder.ak, builder.as)) {
      throw new IllegalArgumentException("host, ak, as must not blank");
    }
    retrofit = createRetrofit();
    retrofitIotService = retrofit.create(RetrofitIotService.class);
  }

  public static Builder builder() {
    return new Builder();
  }

  @SneakyThrows
  public static <T> T execute(Call<T> call) {
    Response<T> response = call.execute();
    if (response.isSuccessful()) {
      return response.body();
    }

    String msg = response.toString();
    if (response.errorBody() != null) {
      String respStr = response.errorBody().string();
      msg += ", body: " + respStr;
      RetrofitIotService.log.error("调用失败，{}", msg);

      if (response.code() != 500) {
        IotErrorResponse result = JsonUtil.fromJson(respStr, IotErrorResponse.class);
        // 设备已经存在
        throw new IotException(result.getCode(), "IoTOS 接口调用失败," + msg);
      }
    }

    throw new IotException(ErrorCode.UNDEFINED.getCode(), "IoTOS 接口调用失败," + msg);
  }

  public <T> T register(Class<T> clazz) {
    return retrofit.create(clazz);
  }

  /**
   * 获取所有产品信息
   *
   * @return List<ConsoleProductDTO>
   */
  public List<Product> getAllProducts() {
    return Pagec.getAll(100, (page, size) -> retrofitIotService.getProduct(page, size));
  }

  /**
   * 创建设备
   *
   * @param addReq
   * @return
   */
  public Device createDevice(DeviceAddReq addReq) {
    return execute(retrofitIotService.createDevice(addReq));
  }

  /**
   * 获取设备信息
   *
   * @param pk pk
   * @param devId 设备ID
   * @return Device
   */
  public Device getDevice(String pk, String devId) {
    return execute(retrofitIotService.getDevice(pk, devId));
  }

  public ModelProtocolDTO getProtocol(String pk) {
    return execute(retrofitIotService.getProtocol(pk));
  }

  private Retrofit createRetrofit() {
    String baseUrl = builder.host;
    if (!baseUrl.endsWith("/")) {
      baseUrl += "/";
    }

    if (!baseUrl.startsWith("http")) {
      baseUrl += "http://";
    }

    HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
    interceptor.setLevel(logLevel);
    OkHttpClient client =
        new OkHttpClient.Builder()
            .addInterceptor(interceptor)
            .addInterceptor(
                chain -> {
                  String reqPath = chain.request().url().uri().getPath();
                  String path = reqPath;
                  if (StringUtils.isNotBlank(builder.getPrefix())) {
                    path = reqPath.substring(builder.getPrefix().length());
                  }
                  Request request =
                      chain
                          .request()
                          .newBuilder()
                          .addHeader(
                              "Authorization", getToken(path, builder.getAk(), builder.getAs()))
                          .addHeader("Content-Type", "application/json")
                          .build();
                  return chain.proceed(request);
                })
            .callTimeout(10, TimeUnit.SECONDS)
            .connectTimeout(3, TimeUnit.SECONDS)
            .build();

    return new Retrofit.Builder()
        .client(client)
        .baseUrl(baseUrl)
        .addConverterFactory(JacksonConverterFactory.create(objectMapper))
        .build();
  }

  @SneakyThrows
  public String getToken(String path, String accessKey, String accessSecret) {
    String timestamp = String.valueOf(System.currentTimeMillis());
    String method = "SHA1";
    return assembleToken(path, timestamp, method, accessKey, accessSecret);
  }

  public byte[] hmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception {
    byte[] data = encryptKey.getBytes();
    // 根据给定的字节数组构造一个密钥，第二参数指定一个密钥算法的名称。
    SecretKey secretKey = new SecretKeySpec(data, MAC_NAME);
    // 生成一个指定 Mac 算法 的 Mac 对象
    Mac mac = Mac.getInstance(MAC_NAME);
    // 用给定密钥初始化 Mac 对象
    mac.init(secretKey);
    byte[] text = encryptText.getBytes();
    // 完成 Mac 操作
    return mac.doFinal(text);
  }

  public String parseByte2HexStr(byte[] buf) {
    if (null == buf) {
      return null;
    }
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < buf.length; i++) {
      String hex = Integer.toHexString(buf[i] & 0xFF);
      if (hex.length() == 1) {
        hex = '0' + hex;
      }
      sb.append(hex.toUpperCase());
    }
    return sb.toString().toLowerCase();
  }

  public String assembleToken(
      String path, String timestamp, String method, String accessKey, String accessSecret)
      throws Exception {
    String encodePath = URLEncoder.encode(path, "UTF-8");
    String data = path + "\n" + timestamp + "\n" + method;
    String signature = parseByte2HexStr(hmacSHA1Encrypt(data, accessSecret));
    return "accessKey="
        + accessKey
        + "&path="
        + encodePath
        + "&timestamp="
        + timestamp
        + "&method="
        + method
        + "&sign="
        + signature;
  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/获取Token.html">获取鉴权信息</a>
   *
   * @return LoginRsp
   */
  LoginRsp getLoginToken() {
    return execute(retrofitIotService.getLoginToken());
  }

  /**
   * 获取产品列表
   *
   * <p><a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/获取产品列表.html">获取产品列表</a>
   *
   * @param page 当前页，从0开始
   * @param size 分页大小，最大100，最小1
   * @return
   */
  public Pagec<Product> getProduct(int page, int size) {
    return execute(retrofitIotService.getProduct(page, size));
  }


  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/网关添加子设备.html">网关添加子设备</a>
   *
   * @param req
   * @return
   */
  public BatchAddDeviceResp addTopo(DevIdListReq req) {
    return execute(retrofitIotService.addTopo(req));
  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/获取批次下所有设备.html">获取批次下所有设备</a>
   *
   * @param pk
   * @param batchName
   * @return
   */
  public List<Device> getBatchDevices(String pk, String batchName) {
    return execute(retrofitIotService.getBatchDevices(pk, batchName));
  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/查询设备列表.html">查询设备列表</a>
   *
   * @param params
   * @return
   */
  public Pagec<Device> getDeviceList(Map<String, Object> params) {
    return execute(retrofitIotService.getDeviceList(params));
  }

  /**
   * *
   *
   * <p><a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/更改设备名称.html">更改设备名称</a>
   *
   * @param req
   * @return
   */
  public void updateName(DeviceUpdateNameReq req) {
    execute(retrofitIotService.updateName(req));
  }

  /**
   * *
   *
   * <p><a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/批量查询状态.html">批量查询状态</a>
   *
   * @param req
   * @return
   */
  public Device getDeviceStatus(DevIdListReq req) {
    return execute(retrofitIotService.getDeviceStatus(req));
  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/删除设备.html">删除设备</a>
   *
   * @param pk
   * @param devId
   * @return
   */
  public Device delDevice(String pk, String devId) {
    return execute(retrofitIotService.delDevice(pk, devId));
  }

//  /**
//   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/查询历史上下行数据.html">查询历史上下行数据"></a>
//   *
//   * @param pk
//   * @param devId
//   * @return
//   */
//  public DevicePacketResp getDeviceHistoryData(String pk, String devId) {
//    return execute(retrofitIotService.getDeviceHistoryData(pk, devId));
//  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/新增设备.html">新增设备</a>
   *
   * @param reqDto
   * @return
   */
  public DeviceApiDTO addDevice(DeviceAddReq reqDto) {
    return execute(retrofitIotService.addDevice(reqDto));
  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/导入设备.html">导入设备</a>
   *
   * @param devIdListReq
   * @return
   */
  public Map<String, Object> batchAddDevices(DevIdListReq devIdListReq) {
    return execute(retrofitIotService.batchAddDevices(devIdListReq));
  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/查询设备列表.html">查询设备列表</a>
   *
   * @param pk
   * @param keyword
   * @param page
   * @param deviceType
   * @param online
   * @param size
   * @return
   */
  public Pagec<DeviceApiDTO> getDeviceList(
      String pk, String keyword, int page, DeviceType deviceType, Boolean online, int size) {
    return execute(retrofitIotService.getDeviceList(pk, keyword, page, deviceType, online, size));
  }

  //  /**
  //   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/更新设备.html">更改设备名称</a>
  //   *
  //   * @param reqDto
  //   */
  //  public Call<?> updateName(DeviceUpdateNameReq reqDto) {
  //    retrofitIotService.updateName(reqDto);
  //  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/???.html">????</a>
   *
   * @param reqDto
   */
  public void updateTags(DeviceAddReq reqDto) {
    execute(retrofitIotService.updateTags(reqDto));
  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/查询设备详情.html">查询设备详情</a>
   *
   * @param pk
   * @param devId
   * @return
   */
  public DeviceApiDTO deviceInfo(String pk, String devId) {
    return execute(retrofitIotService.deviceInfo(pk, devId));
  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/查询设备影子.html">查询设备影子</a>
   *
   * @param pk
   * @param devId
   * @return
   */
  public Snapshot getDeviceSnapshot(String pk, String devId) {
    return execute(retrofitIotService.getDeviceSnapshot(pk, devId));
  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/删除设备.html">删除设备</a>
   *
   * @param pk
   * @param devId
   * @param delSnapshot
   */
  public void delDevice(String pk, String devId, boolean delSnapshot) {
    execute(retrofitIotService.delDevice(pk, devId, delSnapshot));
  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/？？？.html">批量查删除设备</a>
   *
   * @param deviceList
   */
  public void bathDelDevice(List<DeviceDTO> deviceList) {
    execute(retrofitIotService.bathDelDevice(deviceList));
  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/查询历史上下行数.html">查询历史上下行数</a>
   *
   * @param pk
   * @param devId
   * @param startTime
   * @param endTime
   * @param action
   * @param page
   * @param size
   * @return
   */
  public List<DevicePacketResp> getDeviceHistoryData(
      String pk, String devId, Long startTime, Long endTime, String action, int page, int size) {
    return execute(retrofitIotService.getDeviceHistoryData(
      pk, devId, startTime, endTime, action, page, size));
  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/下发控制命令.html">下发控制命令</a>
   *
   * @param pk
   * @param devId
   * @param data
   * @return
   */
  public KlinkResp deviceCloudSend(String pk, String devId, ModelData data) {
    return  execute(retrofitIotService.deviceCloudSend(pk, devId, data));
  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/查询命令状态.html">查询命令状态</a>
   *
   * @param messageId
   * @return
   */
  public CloudSendMsgDTO cloudSendMsgInfo(String messageId) {
    return execute(retrofitIotService.cloudSendMsgInfo(messageId));
  }

  /**
   * * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/查询历史控制命令.html">查询历史控制命令</a>
   *
   * @param page
   * @param size
   * @param pk
   * @param devId
   * @param startTime
   * @param endTime
   * @return
   */
  public Pagec<CloudSendMsgDTO> cloudSendMsgList(
      Integer page, Integer size, String pk, String devId, Long startTime, Long endTime) {
    return execute(retrofitIotService.cloudSendMsgList(page, size, pk, devId, startTime, endTime));
  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/网关添加子设备.html">网关添加子设备</a>
   *
   * @param pk
   * @param devId
   * @param sub
   * @return
   */
  public AddTopoResp addTopo(String pk, String devId, TopoSub sub) {
    return execute(retrofitIotService.addTopo(pk, devId, sub));
  }

  /**
   * <a href="http://hy.hekr.me/iot-docs-test/web/content/应用开发指南/???.html">网关删除子设备</a>
   *
   * @param pk
   * @param devId
   * @param sub
   * @return
   */
  public DelTopoResp delTopo(String pk, String devId, TopoSub sub) {
    return execute(retrofitIotService.delTopo(pk, devId, sub));
  }

  @Data
  public static class Builder {
    private String host;
    private String prefix;
    private String ak;
    private String as;

    private Level level = Level.HEADERS;
    /** 地址，如 http://10.1.1.100:8081 */
    public Builder host(String host) {
      this.host = host;
      return this;
    }

    /** 前缀，比如 /iot-api */
    public Builder prefix(String prefix) {
      this.prefix = prefix;
      return this;
    }

    /** accessKey */
    public Builder ak(String ak) {
      this.ak = ak;
      return this;
    }

    /** accessSecret */
    public Builder as(String as) {
      this.as = as;
      return this;
    }

    public IotClient build() {
      return new IotClient(this);
    }
  }
}
